﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using Microsoft.VisualBasic;
using Microsoft.JScript;
using System.CodeDom;
using TDS = System.Data.Design.TypedDataSetGenerator;

namespace System.Data.DataSetGenerator
{
    public class SqlTypedDataSetGenerator : 
        IMessageSender
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        public SqlTypedDataSetGenerator(SqlConnection connection, DataSetDescriptor descriptor, LanguageEnum language)
        {
            if (descriptor == null)
                throw new ArgumentNullException("descriptor");

            if (String.IsNullOrEmpty(descriptor.DataSetName))
                throw new DataSetGeneratorException("DataSetName property value is undefined. DataSet cannot be generated.");

            if (connection == null)
                throw new ArgumentNullException("connection");

            this.Connection = connection;
            this.DataSetDescriptor = descriptor;

            CommandBuilder = new SqlCommandBuilder();

            Language = language;
        }

        /// <summary>
        /// Only for reverse engineering
        /// </summary>
        /// <param name="descriptor"></param>
        internal SqlTypedDataSetGenerator(DataSetDescriptor descriptor)
        {
            if (descriptor == null)
                throw new ArgumentNullException("descriptor");

            if (String.IsNullOrEmpty(descriptor.DataSetName))
                throw new DataSetGeneratorException("DataSetName property value is undefined. DataSet cannot be generated.");

            this.DataSetDescriptor = descriptor;
            CommandBuilder = new SqlCommandBuilder();

            Language = LanguageEnum.CS;
        }

        private LanguageEnum Language { get; set; }

        protected DataSetDescriptor DataSetDescriptor 
        { 
            get; 
            private set; 
        }

        internal protected DataSet DataSet
        {
            get;
            private set;
        }

        public void Run()
        {
            this.DataSet = new DataSet();
            this.DataSet.DataSetName = this.DataSetDescriptor.DataSetName;

            foreach (TableDescriptor tableDescriptor in DataSetDescriptor.Tables)
            {
                if (String.IsNullOrEmpty(tableDescriptor.TableName))
                    throw new DataSetGeneratorException("Property TableName of the DataTable element (in the DataSet {0}) is undefined", DataSetDescriptor.DataSetName);

                switch (tableDescriptor.Action)
                {
                    case SchemaObjectActionEnum.Add:
                        DataTable dataTable = DataSet.Tables.Add(tableDescriptor.TableName);

                        String sourceTableName = tableDescriptor.TableName;
                        if (!String.IsNullOrEmpty(tableDescriptor.Source))
                        {
                            sourceTableName = tableDescriptor.Source;
                            dataTable.ExtendedProperties.Add("SourceTable", sourceTableName);
                        }

                        // Fill columns
                        FillDataTableColumns(tableDescriptor, dataTable, sourceTableName);

                        // Fill primary keys
                        FillPrimaryKeys(dataTable, sourceTableName);

                        // Fill indexes
                        if (!tableDescriptor.IgnoreIndexes)
                        {
                            FillDataTableIndexes(dataTable, sourceTableName);
                        }
                        break;

                    case SchemaObjectActionEnum.Remove:
                        break;

                    case SchemaObjectActionEnum.Modify:
                        SendMessage("The Modify property for the DataTable element will be ignored", TraceLevel.Warning);
                        break;
                }
            }

            // Make constraints
            if (!DataSetDescriptor.IgnoreForeignKeys)
            {
                FillRelations(DataSetDescriptor, DataSet);
            }

            // Get the XSD output file name
            String outputXsdFileName = DataSetDescriptor.OutputXsdFileName;
            if (DataSetDescriptor.GenerateXsd)
            {
                if (String.IsNullOrEmpty(outputXsdFileName))
                    outputXsdFileName = DataSetDescriptor.DataSetName + ".xsd";
                WriteXsd(outputXsdFileName);
            }

            // Get the output code file file name
            if (DataSetDescriptor.GenerateCode)
            {
                String outputCodeFileName = DataSetDescriptor.OutputCodeFileName;
                if (String.IsNullOrEmpty(outputCodeFileName))
                {
                    outputCodeFileName = outputXsdFileName.Replace(".xsd", "");
                    outputCodeFileName = outputCodeFileName + ".Designer." + this.Language.ToString().ToLower();
                }
                WriteCode(outputCodeFileName, Language, DataSetDescriptor.Namespace);
            }
        }

        /// <summary>
        /// Internal SQL Connection
        /// </summary>
        public SqlConnection Connection { get; set; }

        /// <summary>
        /// Internal CommandBuilder
        /// </summary>
        private SqlCommandBuilder CommandBuilder {get;set;}

        protected virtual void FillDataTableColumns(TableDescriptor tableDescriptor, DataTable dataTable, String tableName)
        {
            dataTable.Columns.Clear();

            // Prepare the given DataTable
            using (SqlCommand cmd = Connection.CreateCommand())
            {
                String sql = String.Format("select * from {0} where 0 = 1", CommandBuilder.QuoteIdentifier(tableName));
                cmd.CommandText = sql;
                using (SqlDataAdapter ad = new SqlDataAdapter(cmd))
                {
                    ad.MissingSchemaAction = MissingSchemaAction.AddWithKey;
                    ad.Fill(dataTable);
                }
            }

            // Process some columns
            foreach (DataColumn column in dataTable.Columns)
            {
                if (column.AutoIncrement)
                {
                    column.AutoIncrementSeed = tableDescriptor.AutoincrementSeed;
                    column.AutoIncrementStep = tableDescriptor.AutoincrementStep;
                }
            }

            // Columns to exclude
            foreach (ColumnDescriptor columnDescriptor in tableDescriptor.ColumnsToExclude)
            {
                if (String.IsNullOrEmpty(columnDescriptor.ColumnName))
                    throw new DataSetGeneratorException("ColumnName property value of the DataColumn element is undefined");

                if (dataTable.Columns.Contains(columnDescriptor.ColumnName))
                {
                    dataTable.Columns.Remove(columnDescriptor.ColumnName);

                    String message = String.Format("    DataColumn {0} has been removed from the DataTable {1}", columnDescriptor.ColumnName, dataTable.TableName);
                    SendMessage(message, TraceLevel.Verbose);
                }
            }

            // Columns to include
            foreach (ColumnDescriptor columnDescriptor in tableDescriptor.ColumnsToInclude)
            {
                DataColumn dataColumn;
                if (dataTable.Columns.Contains(columnDescriptor.ColumnName))
                {
                    dataColumn = dataTable.Columns[columnDescriptor.ColumnName];
                    dataColumn.DataType = columnDescriptor.GetDataType();
                }
                else
                {
                    dataColumn = dataTable.Columns.Add(columnDescriptor.ColumnName, columnDescriptor.GetDataType());

                    String message = String.Format("    DataColumn {0} of type {1} has been added to the DataTable {2}", dataColumn.ColumnName, dataColumn.DataType, dataTable.TableName);
                    SendMessage(message, TraceLevel.Verbose);
                }
                dataColumn.AllowDBNull = columnDescriptor.AllowDBNull;
                dataColumn.Caption = columnDescriptor.Caption;
                dataColumn.DefaultValue = columnDescriptor.DefaultValue;
                dataColumn.ReadOnly = columnDescriptor.ReadOnly;
                dataColumn.Unique = columnDescriptor.Unique;
                dataColumn.Expression = columnDescriptor.Expression;
            }
        }

        protected virtual void FillPrimaryKeys(DataTable dataTable, String sourceTableName)
        {
            dataTable.PrimaryKey = new DataColumn[0];
            String sql = String.Format(@"
              select tc.CONSTRAINT_SCHEMA,
                tc.CONSTRAINT_NAME,
                pkcol.ORDINAL_POSITION,
                pkcol.TABLE_SCHEMA as pkSchema,
                pkcol.TABLE_NAME as pkTable,
                pkcol.COLUMN_NAME as pkColumn
	          from INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc,
		           INFORMATION_SCHEMA.KEY_COLUMN_USAGE as pkcol
              where tc.CONSTRAINT_SCHEMA = pkcol.CONSTRAINT_SCHEMA and
                    tc.CONSTRAINT_NAME = pkcol.CONSTRAINT_NAME and
                    tc.CONSTRAINT_TYPE = 'PRIMARY KEY' and
                    ISNULL(OBJECTPROPERTY(OBJECT_ID(tc.TABLE_NAME), 'IsMSShipped'), 0) = 0 and
                    pkcol.TABLE_NAME = '{0}'
            
              order by 
                 tc.CONSTRAINT_SCHEMA,
                 tc.CONSTRAINT_NAME,
                 pkcol.ORDINAL_POSITION 
            ", sourceTableName);
            DataTable pkDataTable = new DataTable();

            using (SqlCommand cmd = Connection.CreateCommand())
            {    
                cmd.CommandText = sql;
                using (SqlDataAdapter ad = new SqlDataAdapter(cmd))
                {
                    ad.MissingSchemaAction = MissingSchemaAction.Add;
                    ad.Fill(pkDataTable); 
                }
            }

            List<DataColumn> list = new List<DataColumn>();
            String pkName = "";

            foreach (DataRow row in pkDataTable.Rows)
            {
                pkName = row["CONSTRAINT_NAME"].ToString();
                list.Add(dataTable.Columns[row["pkColumn"].ToString()]);
            }

            if (list.Count != 0)
            {
                UniqueConstraint pkConstraint = new UniqueConstraint(list.ToArray(), true);
                if (!String.IsNullOrEmpty(pkName))
                    pkConstraint.ConstraintName = pkName;
                dataTable.Constraints.Add(pkConstraint);
                String message = String.Format("    Primary Key {0} has been added to the DataTable {1}", pkName, dataTable.TableName);
                SendMessage(message, TraceLevel.Verbose);
            }
        }

        protected virtual void FillDataTableIndexes(DataTable dataTable, String sourceTableName)
        {
            String sql = String.Format(@"
select tc.CONSTRAINT_SCHEMA,
tc.CONSTRAINT_NAME,
pkcol.ORDINAL_POSITION,
(select count(*) 
    from INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc
    where tc.CONSTRAINT_SCHEMA = kc.CONSTRAINT_SCHEMA and
          tc.CONSTRAINT_NAME = kc.CONSTRAINT_NAME and
          ISNULL(OBJECTPROPERTY(OBJECT_ID(tc.TABLE_NAME), 'IsMSShipped'), 0) = 0)
          as COUNT,
pkcol.TABLE_SCHEMA as pkSchema,
pkcol.TABLE_NAME as pkTable,
pkcol.COLUMN_NAME as pkColumn
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc,
   INFORMATION_SCHEMA.KEY_COLUMN_USAGE as pkcol
where tc.CONSTRAINT_SCHEMA = pkcol.CONSTRAINT_SCHEMA and
    tc.CONSTRAINT_NAME = pkcol.CONSTRAINT_NAME and
    tc.CONSTRAINT_TYPE = 'UNIQUE' and
    ISNULL(OBJECTPROPERTY(OBJECT_ID(tc.TABLE_NAME), 'IsMSShipped'), 0) = 0 and
    pkcol.TABLE_NAME = '{0}'
order by 
 tc.CONSTRAINT_SCHEMA,
 tc.CONSTRAINT_NAME,
 pkcol.ORDINAL_POSITION", sourceTableName);

            DataTable dataTableIndexes = new DataTable();
            using (SqlCommand cmd = Connection.CreateCommand())
            {
                cmd.CommandText = sql;
                using (SqlDataAdapter ad = new SqlDataAdapter(cmd))
                {
                    ad.MissingSchemaAction = MissingSchemaAction.Add;
                    ad.Fill(dataTableIndexes);

                    var idxNames = dataTableIndexes.AsEnumerable()
                        .Select(row => row["CONSTRAINT_NAME"].ToString())
                        .Distinct()
                        .ToArray();

                    foreach (var idxName in idxNames)
                    {
                        var colNames = dataTableIndexes.AsEnumerable()
                            .Where(row => row["CONSTRAINT_NAME"].ToString() == idxName)
                            .OrderBy(row => Convert.ToInt32(row["ORDINAL_POSITION"]))
                            .Select(row => row["pkColumn"].ToString())
                            .ToArray();

                        List<DataColumn> indexColumns = new List<DataColumn>();

                        foreach (var colName in colNames)
                        {
                            indexColumns.Add(dataTable.Columns[colName]);
                        }
                        UniqueConstraint uniqueConstraint = new UniqueConstraint(idxName, indexColumns.ToArray());
                        dataTable.Constraints.Add(uniqueConstraint);

                        String message = String.Format("    Unique index {0} has been added to the DataTable {1}", uniqueConstraint.ConstraintName, dataTable.TableName);
                        SendMessage(message, TraceLevel.Verbose);
                    }
                }
            }
        }

        protected virtual void FillRelations(DataSetDescriptor DataSetDescriptor, DataSet DataSet)
        {
            foreach (DataTable dataTable in DataSet.Tables)
            {
                String tableName = dataTable.TableName;

                String sql = String.Format(@"
              select 
                rc.CONSTRAINT_SCHEMA,
                rc.CONSTRAINT_NAME,
                pkcol.ORDINAL_POSITION,
                (select count(*) from INFORMATION_SCHEMA.KEY_COLUMN_USAGE kc
                    where rc.CONSTRAINT_SCHEMA = kc.CONSTRAINT_SCHEMA and
                          rc.CONSTRAINT_NAME = kc.CONSTRAINT_NAME and
                          ISNULL(OBJECTPROPERTY(OBJECT_ID(kc.CONSTRAINT_NAME), 'IsMSShipped'), 0) = 0)
                          as COUNT,
	            fkcol.TABLE_SCHEMA as fkSchema,
                fkcol.TABLE_NAME as fkTable,
                fkcol.COLUMN_NAME as fkColumn,
                pkcol.TABLE_SCHEMA as pkSchema,
                pkcol.TABLE_NAME as pkTable,
                pkcol.COLUMN_NAME as pkColumn,
                rc.UPDATE_RULE,
                rc.DELETE_RULE
	          from INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc,
                   INFORMATION_SCHEMA.KEY_COLUMN_USAGE as pkcol,
                   INFORMATION_SCHEMA.KEY_COLUMN_USAGE as fkcol
              where rc.CONSTRAINT_SCHEMA = fkcol.CONSTRAINT_SCHEMA and
                 rc.CONSTRAINT_NAME = fkcol.CONSTRAINT_NAME and
                 rc.UNIQUE_CONSTRAINT_SCHEMA = pkcol.CONSTRAINT_SCHEMA and
                 rc.UNIQUE_CONSTRAINT_NAME = pkcol.CONSTRAINT_NAME and
                 pkcol.ORDINAL_POSITION = fkcol.ORDINAL_POSITION and
                 ISNULL(OBJECTPROPERTY(OBJECT_ID(rc.CONSTRAINT_NAME), 'IsMSShipped'), 0) = 0 and
                 fkcol.TABLE_NAME = '{0}'
              order by 
                 rc.CONSTRAINT_SCHEMA,
                 rc.CONSTRAINT_NAME,
                 pkcol.ORDINAL_POSITION", tableName);

                DataTable fkDataTable = new DataTable();

                using (SqlCommand cmd = Connection.CreateCommand())
                {
                    cmd.CommandText = sql;
                    
                    using (SqlDataAdapter ad = new SqlDataAdapter(cmd))
                    {
                        ad.MissingSchemaAction = MissingSchemaAction.Add;
                        ad.Fill(fkDataTable);
                    }
                }

                var fkNames = fkDataTable.AsEnumerable()
                    .Select(row => row["CONSTRAINT_NAME"].ToString())
                    .Distinct()
                    .ToArray();

                // Iterate through the each constraint name
                foreach (var fkName in fkNames)
                {
                    var fkRows = fkDataTable.AsEnumerable()
                        .Where(row1 => row1["CONSTRAINT_NAME"].ToString() == fkName)
                        .ToArray();
                    Rule updateRule = Rule.None;
                    Rule deleteRule = Rule.None;
                    String pkTableName = "";

                    DataTable pkDataTable = null;

                    if (fkRows.Length != 0)
                    {
                        pkTableName = fkRows[0]["pkTable"].ToString();
                        if (DataSet.Tables.Contains(pkTableName))
                        {
                            pkDataTable = DataSet.Tables[pkTableName];
                        }

                        if ("CASCADE".Equals(fkRows[0]["UPDATE_RULE"].ToString()))
                        {
                            updateRule = Rule.Cascade;
                        }

                        if ("CASCADE".Equals(fkRows[0]["DELETE_RULE"].ToString()))
                        {
                            deleteRule = Rule.Cascade;
                        }
                    }
                    if (pkDataTable != null)
                    {
                        List<DataColumn> fkColumns = new List<DataColumn>();
                        List<DataColumn> pkColumns = new List<DataColumn>();

                        foreach (var fkRow in fkRows)
                        {
                            pkColumns.Add(pkDataTable.Columns[fkRow["pkColumn"].ToString()]);
                            fkColumns.Add(dataTable.Columns[fkRow["fkColumn"].ToString()]);
                        }

                        RelationDescriptor relationDescriptor = DataSetDescriptor.Relations[fkName];

                        if (relationDescriptor != null)
                        {
                            // Correct relation types and rules
                            if (relationDescriptor.Action == SchemaObjectActionEnum.Modify)
                            {
                                if ((relationDescriptor.RelationType & RelationTypeEnum.Constraint) == RelationTypeEnum.Constraint)
                                {
                                    ForeignKeyConstraint fkConstraint = new ForeignKeyConstraint(fkName, pkColumns.ToArray(), fkColumns.ToArray());
                                    fkConstraint.UpdateRule = relationDescriptor.UpdateRule;
                                    fkConstraint.DeleteRule = relationDescriptor.DeleteRule;
                                    dataTable.Constraints.Add(fkConstraint);

                                    String message = String.Format("    ForeignKeyConstraint {0} has been created", fkConstraint.ConstraintName);
                                    SendMessage(message, TraceLevel.Verbose);
                                }
                                if ((relationDescriptor.RelationType & RelationTypeEnum.Relation) == RelationTypeEnum.Relation)
                                {
                                    DataRelation fkRelation = new DataRelation(fkName, pkColumns.ToArray(), fkColumns.ToArray(), false);
                                    DataSet.Relations.Add(fkRelation);

                                    String message = String.Format("    Relation {0} has been created", fkRelation.RelationName);
                                    SendMessage(message, TraceLevel.Verbose);
                                }
                            }

                            // Ignore relation
                            if (relationDescriptor.Action == SchemaObjectActionEnum.Remove)
                            {
                                String message = String.Format("    Relation {0} has been ignored", relationDescriptor.RelationName);
                                SendMessage(message, TraceLevel.Verbose);
                            }
                        }
                        else
                        {
                            // Default database foreign key
                            ForeignKeyConstraint fkConstraint = new ForeignKeyConstraint(fkName, pkColumns.ToArray(), fkColumns.ToArray());
                            fkConstraint.UpdateRule = updateRule;
                            fkConstraint.DeleteRule = deleteRule;
                            dataTable.Constraints.Add(fkConstraint);

                            String message = String.Format("    ForeignKeyConstraint {0} has been created", fkConstraint.ConstraintName);
                            SendMessage(message, TraceLevel.Verbose);

                            DataRelation fkRelation = new DataRelation(fkName, pkColumns.ToArray(), fkColumns.ToArray(), true);
                            DataSet.Relations.Add(fkRelation);

                            message = String.Format("    Relation {0} has been created", fkRelation.RelationName);
                            SendMessage(message, TraceLevel.Verbose);
                        }
                    }
                }
            }

            // Postprocess correction

            foreach (RelationDescriptor relationDescriptor in DataSetDescriptor.Relations.Where(r => r.Action == SchemaObjectActionEnum.Add))
            {
                if (
                    !String.IsNullOrEmpty(relationDescriptor.PKDataTable) ||
                    !String.IsNullOrEmpty(relationDescriptor.PKDataColumns) ||
                    !String.IsNullOrEmpty(relationDescriptor.FKDataTable) ||
                    !String.IsNullOrEmpty(relationDescriptor.FKDataColumns)
                )
                {
                    if (!DataSet.Tables.Contains(relationDescriptor.PKDataTable))
                    {
                        String message = String.Format("Cannot modify relation '{0}': datatable '{1}' is not in the dataset", relationDescriptor.RelationName, relationDescriptor.PKDataTable);
                        SendMessage(message, TraceLevel.Info);
                        goto __NEXT;
                    }
                    if (!DataSet.Tables.Contains(relationDescriptor.FKDataTable))
                    {
                        String message = String.Format("Cannot modify relation '{0}': datatable '{1}' is not in the dataset", relationDescriptor.RelationName, relationDescriptor.FKDataTable);
                        SendMessage(message, TraceLevel.Info);
                        goto __NEXT;
                    }
                    DataTable pkDataTable = DataSet.Tables[relationDescriptor.PKDataTable];
                    DataTable fkDataTable = DataSet.Tables[relationDescriptor.FKDataTable];

                    List<DataColumn> pkColumns = new List<DataColumn>();
                    List<DataColumn> fkColumns = new List<DataColumn>();

                    String[] pkColumnNames = relationDescriptor.PKDataColumns.Split(',').Select(n => n.Trim()).ToArray();
                    String[] fkColumnNames = relationDescriptor.FKDataColumns.Split(',').Select(n => n.Trim()).ToArray();
                    foreach (var pkColumnName in pkColumnNames)
                    {
                        pkColumns.Add(pkDataTable.Columns[pkColumnName]);
                    }
                    foreach (var fkColumnName in fkColumnNames)
                    {
                        fkColumns.Add(fkDataTable.Columns[fkColumnName]);
                    }
                    if (pkColumns.Count != fkColumns.Count)
                    {
                        String errorMessage = String.Format("Cannot correct relation '{0}' in the dataset '{1}': check relation description", relationDescriptor.RelationName, DataSet.DataSetName);
                        throw new ApplicationException(errorMessage);
                    }

                    if (DataSet.Relations.Contains(relationDescriptor.RelationName))
                        DataSet.Relations.Remove(relationDescriptor.RelationName);

                    if ((relationDescriptor.RelationType & RelationTypeEnum.Constraint) == RelationTypeEnum.Constraint)
                    {
                        ForeignKeyConstraint fkConstraint = new ForeignKeyConstraint(relationDescriptor.RelationName, pkColumns.ToArray(), fkColumns.ToArray());
                        fkConstraint.UpdateRule = relationDescriptor.UpdateRule;
                        fkConstraint.DeleteRule = relationDescriptor.DeleteRule;
                        if (fkDataTable.Constraints.Contains(relationDescriptor.RelationName))
                            fkDataTable.Constraints.Remove(relationDescriptor.RelationName);
                        fkDataTable.Constraints.Add(fkConstraint);
                    }
                    if ((relationDescriptor.RelationType & RelationTypeEnum.Relation) == RelationTypeEnum.Relation)
                    {
                        DataRelation fkRelation = new DataRelation(relationDescriptor.RelationName, pkColumns.ToArray(), fkColumns.ToArray(), false);
                        DataSet.Relations.Add(fkRelation);
                    }
                }
            __NEXT: ;
            }
        }

        protected void SendMessage(String message, TraceLevel level)
        {
            if (Message != null)
            {
                MessageEventArgs e = new MessageEventArgs(message, level);
                Message(this, e);
            }
        }

        public event MessageEventHandler Message;

        /// <summary>
        /// Returns the XML Schema for the XML representation of the data stored in the DataSet.
        /// </summary>
        /// <returns></returns>
        public String GetXmlSchema()
        {
            return DataSet.GetXmlSchema();
        }

        /// <summary>
        /// Returns the XML Schema for the XML representation of the data stored in the DataSet,
        /// in the XmlDocument instance
        /// </summary>
        /// <returns></returns>
        public XmlDocument GetXmlSchemaAsXmlDocument()
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(DataSet.GetXmlSchema());
            return doc;
        }

        /// <summary>
        /// Serializes the Xsd schema to a specified stream. 
        /// </summary>
        /// <param name="stream"></param>
        public void WriteXsd(Stream stream)
        {
            GetXmlSchemaAsXmlDocument().Save(stream);
        }


        /// <summary>
        /// Serializes the annotation to a specified file. 
        /// </summary>
        /// <param name="fileName"></param>
        public void WriteXsd(String fileName)
        {
            FileUtil.ClearReadOnlyFlag(fileName);
            GetXmlSchemaAsXmlDocument().Save(fileName);

            String message = String.Format("{0}: XSD Schema has been saved to {1}", DataSetDescriptor.DataSetName, fileName);
            SendMessage(message, TraceLevel.Info);
        }

        /// <summary>
        /// Initializes a code provider
        /// </summary>
        /// <param name="language"></param>
        /// <param name="codeProvider"></param>
        /// <param name="fileExtension"></param>
        private void CreateCodeProvider(LanguageEnum language, ref CodeDomProvider codeProvider, ref string fileExtension)
        {
            switch (language)
            {
                case LanguageEnum.CS:
                    codeProvider = new CSharpCodeProvider();
                    fileExtension = ".cs";
                    break;
                case LanguageEnum.VB:
                    codeProvider = new VBCodeProvider();
                    fileExtension = ".vb";
                    break;
                case LanguageEnum.JS:
                    codeProvider = new JScriptCodeProvider();
                    fileExtension = ".js";
                    break;
            }
        }


        /// <summary>
        /// Serializes the generated code to a specified stream. 
        /// </summary>
        /// <param name="stream">The stream to use to serialize code</param>
        /// <param name="language">The programmation language (CS, VB, or JS)</param>
        /// <param name="ns">The namespace</param>
        /// <remarks>
        /// Do not change the Stream.Position propert value during batch generator calls.
        /// </remarks>
        public void WriteCode(Stream stream, LanguageEnum language, String ns)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            if (ns == null)
                ns = String.Empty;

            // Code provider
            CodeDomProvider codeProvider = null;
            String fileExtension = "";
            CreateCodeProvider(language, ref codeProvider, ref fileExtension);
            CodeCompileUnit compileUnit = new CodeCompileUnit();
            CodeNamespace codeNamespace = new CodeNamespace(ns);
            compileUnit.Namespaces.Add(codeNamespace);

            // Run
            String result = TDS.Generate(
                DataSet.GetXmlSchema(),
                compileUnit,
                codeNamespace,
                codeProvider,
                TDS.GenerateOption.LinqOverTypedDatasets | TDS.GenerateOption.HierarchicalUpdate);

            StreamWriter writer = new StreamWriter(stream, Encoding.UTF8);
            codeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, null);
            writer.Flush();
        }

        /// <summary>
        /// Serializes the generated code to a specified file. 
        /// </summary>
        /// <param name="fileName">The output file name to use to serialize code</param>
        /// <param name="language">Defines the programmic language (CS, VB, or JS)</param>
        /// <param name="ns">The namespace</param>
        public void WriteCode(String fileName, LanguageEnum language, String ns)
        {
            FileUtil.ClearReadOnlyFlag(fileName);

            using (FileStream stm = new FileStream(fileName, FileMode.Create))
            {
                WriteCode(stm, language, ns);
                stm.Flush();
                stm.Close();
            }

            String message = String.Format("{0}: code file has been saved to {1}", DataSetDescriptor.DataSetName, fileName);
            SendMessage(message, TraceLevel.Info);
        }
    }
}
